import csv
import json
import os
import shutil
import zipfile
from pathlib import Path
import unittest
from unittest import mock
from packageship import BASE_PATH
from packageship.application.database.session import DatabaseSession
from packageship.libs.conf import configuration


class PanelMixin(unittest.TestCase):
    def setUp(self) -> None:
        """
        Client Test setup
        Set Environment variable "SETTINGS_FILE_PATH" to project pacjage.ini path
        """

        os.environ["SETTINGS_FILE_PATH"] = str(Path(BASE_PATH, "package.ini"))
        from packageship_panel.application import init_app

        app = init_app("query")
        self.session = DatabaseSession().connection()
        self.client = app.test_client()
        self._to_clean_patchers = []
        self._to_clean_mock_patchers = []

    @staticmethod
    def read_json_file(file_path):
        try:
            with open(file_path, "r", encoding="utf8") as fp:
                json_data = json.load(fp)
                return json_data
        except FileNotFoundError:
            return []

    def _create_patch(self, mock_name, **kwargs):
        """create_patch

        Args:
            method_name (str): mock method or attribute name

        """
        patcher = mock.patch(mock_name, **kwargs)
        self._to_clean_mock_patchers.append(patcher)
        patcher.start()

    def _to_update_kw_and_make_mock(self, mock_name, effect=None, **kwargs):
        """_to_update_kw_and_make_mock

        Args:
            mock_name (str):  mock method or attribute name
            effect (Any, optional): side effect value

        Raises:
            ValueError: If the side_effect or return_value keyword parameter is not specified
                        specify the value of the effect keyword parameter
        """
        if "side_effect" not in kwargs and "return_value" not in kwargs:
            if effect is None:
                raise ValueError(
                    "If the side_effect or return_value keyword parameter is not specified,"
                    "specify the value of the effect keyword parameter"
                )
            kwargs["side_effect"] = effect
        self._create_patch(mock_name, **kwargs)

    def _es_scan_result(self, client, index, query, scroll="3m", timeout="1m"):
        """_es_scan_result

        Args:
            client : es clinet
            index : query es index
            query : query es body
            scroll (str, optional):  Defaults to '3m'.
            timeout (str, optional):  Defaults to '1m'.

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _es_search_result(self, index, body):
        """_es_search_result

        Args:
            index (str): query es index
            body (dict): query es body

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _es_delete_result(self, *args, **kwargs):
        """_es_delete_result

        Raises:
            NotImplementedError: [description]
        """
        raise NotImplementedError

    def _es_count_result(self, index, body):
        """_es_count_result

        Args:
            index (str): query es index
            body (dict): query es body
            doc_type (str): doc type

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _redis_hmest_result(self, *args, **kwargs):
        """_es_count_result

        Args:
            index (str): query es index
            body (dict): query es body
            doc_type (str): doc type

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _yaml_load_result(self, stream, Loader=None):
        """_es_count_result

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _redis_set(
        self, name, value, ex=None, px=None, nx=False, xx=False, keepttl=False
    ):
        raise NotImplementedError

    def mock_db_session_scan(self, **kwargs):

        """mock_es_scan"""
        self._to_update_kw_and_make_mock(
            "elasticsearch.helpers.scan", effect=self._es_scan_result, **kwargs
        )

    def _es_async_bulk(self, stream, Loader=None):
        """_es_count_result

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def _smtplib_login(self, **kwargs):

        raise NotImplementedError

    def mock_smtplib_login(self, **kwargs):

        """mock_es_scan"""
        self._to_update_kw_and_make_mock(
            "smtplib.SMTP_SSL.login", effect=self._smtplib_login, **kwargs
        )

    def _smtplib_sendmail(self, **kwargs):

        raise NotImplementedError

    def mock_smtplib_sendmail(self, **kwargs):

        """mock_es_scan"""
        self._to_update_kw_and_make_mock(
            "smtplib.SMTP_SSL.sendmail", effect=self._smtplib_sendmail, **kwargs
        )

    def mock_smtplib_smtp_ssl(self, **kwargs):

        """mock_es_scan"""
        self._to_update_kw_and_make_mock(
            "smtplib.SMTP_SSL", effect=self._smtp_stmp_ssl, **kwargs
        )

    def mock_db_session_async_bulk(self, **kwargs):

        """mock_es_scan"""
        self._to_update_kw_and_make_mock(
            "elasticsearch.helpers.async_bulk", effect=self._es_async_bulk, **kwargs
        )

    def mock_db_session_delete(self, **kwargs):
        """mock elasticsearch delete"""
        self._to_update_kw_and_make_mock(
            "elasticsearch.client.indices.IndicesClient.delete",
            effect=self._es_delete_result,
            **kwargs,
        )

    def mock_db_session_search(self, **kwargs):
        """mock_es_query"""
        self._to_update_kw_and_make_mock(
            "elasticsearch.Elasticsearch.search",
            effect=self._es_search_result,
            **kwargs,
        )

    def mock_db_session_count(self, **kwargs):
        """mock_es_count"""
        self._to_update_kw_and_make_mock(
            "elasticsearch.Elasticsearch.count", effect=self._es_count_result, **kwargs
        )

    def mock_yaml_load(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "yaml.load", effect=self._yaml_load_result, **kwargs
        )

    def mock_redis_set(self, **kwargs):
        self._to_update_kw_and_make_mock(
            "redis.Redis.set", effect=self._redis_set, **kwargs
        )

    def compare_resp(self, responses, expect):
        if responses:
            hash_responses = hash(str(sorted(responses)))
            hash_expect = hash(str(sorted(responses)))
            self.assertEqual(hash_responses, hash_expect)
        else:
            self.assertEqual(responses, expect)

    def compare_response_and_expect(self, response, expect):
        self.assertEqual(response["code"], expect["code"])
        self.assertEqual(response["message"], expect["message"])

        self.compare_resp(response["resp"], expect["resp"])

    def mock_redis_hmset(self, **kwargs):
        """mock_redis_exists_side_effect"""
        self._to_update_kw_and_make_mock(
            "packageship.application.common.constant.REDIS_CONN.hmset",
            effect=self._redis_hmest_result,
            **kwargs,
        )

    def _redis_hgetall_result(self, *args, **kwargs):
        """_es_count_result

        Args:
            index (str): query es index
            body (dict): query es body
            doc_type (str): doc type

        Raises:
            NotImplementedError:
        """
        raise NotImplementedError

    def mock_redis_hgetall(self, **kwargs):
        """mock_redis_exists_side_effect"""
        self._to_update_kw_and_make_mock(
            "packageship.application.common.constant.REDIS_CONN.hgetall",
            effect=self._redis_hgetall_result,
            **kwargs,
        )

    @staticmethod
    def data_loads(data):
        return json.loads(data)

    def _to_add_cleanup(self):
        """_to_add_cleanup"""
        for tc in self._to_clean_mock_patchers:
            self.addCleanup(tc.stop)
        self._to_clean_mock_patchers.clear()

    @staticmethod
    def mock_data_path(folder, file_name, choice=False):
        if not choice:
            return str(
                Path(Path(__file__).parents[0]).joinpath(folder, "mock_data", file_name)
            )
        return str(
            Path(Path(__file__).parents[0]).joinpath(folder, "except_data", file_name)
        )

    def test_tmp_path(self):
        base_path = Path(__file__).absolute().parents[0]
        base_path.joinpath("tmp_zip").mkdir(parents=True, exist_ok=True)
        base_path.joinpath("down_zip").mkdir(parents=True, exist_ok=True)

        return str(base_path.joinpath("tmp_zip")), str(base_path.joinpath("down_zip"))

    @staticmethod
    def decompress_zip(zip_path, tmp_path):
        zf = zipfile.ZipFile(zip_path, "r")

        zf.extract(zf.namelist()[0], tmp_path)
        return zf.namelist()[0]

    @staticmethod
    def save_response_content(path, content):
        with open(path, "wb") as f:
            f.write(content)

    @staticmethod
    def read_csv_file(csv_path):
        """

        Args:
            csv_path:

        Returns:

        """
        with open(csv_path, "r", encoding="utf-8") as csv_content:
            cvs_reader = csv.reader(csv_content)
            column_actual = [row for row in cvs_reader]
        return column_actual

    @staticmethod
    def read_xml_file(folder, file_name):
        """

        Args:
            folder:
            file_name:
        Returns:

        """
        file_path = str(
            Path(Path(__file__).parents[0]).joinpath(folder, "mock_data", file_name)
        )
        with open(file_path, "r", encoding="utf-8") as file:
            return file.read()

    def compare_csv_data(self, actual_data, except_data):
        for index, data in enumerate(actual_data):
            if data:
                self.assertEqual(set(data), set(except_data[index]))

    def clear_file(self):
        base_path = Path(__file__).absolute().parents[0]
        if base_path.joinpath("tmp_zip").exists():
            shutil.rmtree(base_path.joinpath("tmp_zip"))
        if base_path.joinpath("down_zip").exists():
            shutil.rmtree(base_path.joinpath("down_zip"))

    def tearDown(self) -> None:
        self.clear_file()
        self._to_add_cleanup()
        return super().tearDown()


class Dict(dict):
    __setattr__ = dict.__setitem__
    __getattr__ = dict.__getitem__


def dict_to_object(dictObj):
    if not isinstance(dictObj, dict):
        return dictObj
    inst = Dict()
    for k, v in dictObj.items():
        inst[k] = dict_to_object(v)
    return inst
